/***************************************************************************
 * This program is free software; you can redistribute it and/or           *
 * modify it under the terms of the GNU General Public License             *
 * as published by the Free Software Foundation; either version 2          *
 * of the License, or (at your option) any later version.                  *
 *                                                                         *
 * This program is distributed in the hope that it will be useful,         *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
 * GNU General Public License for more details.                            *
 *                                                                         *
 * You should have received a copy of the GNU General Public License       *
 * along with this program; if not, write to the Free Software Foundation, *
 * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.         *
 *                                                                         *
 *   Copyright (C) 2007,2008 by Ivan Vucica                                *
 *   ivucica@gmail.com                                                     *
 ***************************************************************************/

#include <stdlib.h>
#include <GLICT/container.h>
#include <GLICT/panel.h>
#include <GLICT/fonts.h>
#include <GLICT/textbox.h>
#include <GLICT/globals.h>
#include <GL/glut.h>

#include <sstream>
#include <stdarg.h>
#include <iostream>
#include <fstream>

#include "glutfont.h"
#include "logocontext.h"
#include "logointerpreter.h"
#include "primlib.h"
#include "turtle.h"
glictContainer desktop;
glictPanel graphics;
glictPanel commander;
glictPanel cmdconsole;
glictPanel cmdconsoletext;
glictTextbox command;
LogoContext globalcontext;

std::stringstream console;
int windowhandle; // glut's window identifier
extern Turtle turtle;
extern Color onlogo_screen[];

void onlogo_printf (const char* txt, ...) {
	va_list vl;
	va_start(vl, txt);
	static char tx[10000];
	vsnprintf(tx, 10000, txt, vl);
	console << tx;
	va_end(vl);
}


void onpaint(glictRect* real, glictRect* clipped, glictContainer* callerclass) {

	static unsigned char rgb[640*480*3];

    glPushAttrib(GL_VIEWPORT);
    glViewport(clipped->left, glictGlobals.h - clipped->bottom , clipped->right - clipped->left, clipped->bottom - clipped->top);


    glMatrixMode(GL_PROJECTION);
    //glPushMatrix();
    glLoadIdentity();
    //gluOrtho2D(-320, 320, -240, 240);
    gluOrtho2D(-glictGlobals.w / 4, glictGlobals.w / 4, -(glictGlobals.h - 120.) / 4 , (glictGlobals.h - 120.) / 4 );




    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();



	for (int i=0;i<640*480;i++) {
		unsigned char hval = (onlogo_screen[i].h!=0) ? 255 : 127;

		rgb[i*3+0] = onlogo_screen[i].r * hval;
		rgb[i*3+1] = onlogo_screen[i].g * hval;
		rgb[i*3+2] = onlogo_screen[i].b * hval;

		//if (hval != 127) exit(1);

	}
	glEnable(GL_TEXTURE_2D);
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, 640,
                    480, GL_RGB, GL_UNSIGNED_BYTE, rgb);
	glBegin(GL_QUADS);
	glTexCoord2f(0,0);
	glVertex2f(-320, -240);
	glTexCoord2f(0,1);
	glVertex2f(-320, 240);
	glTexCoord2f(1,1);
	glVertex2f(320, 240);
	glTexCoord2f(1,0);
	glVertex2f(320, -240);
	glEnd();
	glDisable(GL_TEXTURE_2D);


	if (turtle.IsVisible()) {

		glTranslatef(turtle.GetX(), turtle.GetY(), 0);
		glRotatef(90. - turtle.GetRot() , 0., 0., 1.);
		unsigned char hval = (turtle.GetColor().h!=0) ? 255 : 127;
		glColor3f(turtle.GetColor().r * hval, turtle.GetColor().g * hval, turtle.GetColor().b * hval);

		glBegin(GL_LINE_LOOP);

		glVertex2f(0, -10);
		glVertex2f(-5, 5);
		glVertex2f(5, 5);

		glVertex2f(0, -10);
		glVertex2f(-5, 5);
		glVertex2f(5, 5);
		glEnd();
	}
    glColor3f(1,1,1);
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, glictGlobals.w, 0, glictGlobals.h);

	glRotatef(180.0, 1.0, 0.0, 0.0);
	glTranslatef(0,-glictGlobals.h,0.0);
    //glPopMatrix();

	glMatrixMode(GL_MODELVIEW);
    glPopAttrib();
}


// displays stuff, glut's way of doing stuff
void display() {

    // make sure the stenciltest is off before clearing
    // in this example we're sure so we dont enforce it
    glClearColor(0.0,0.0,0.0,0.0);
    glClear(GL_COLOR_BUFFER_BIT);


    // below's the sample of glict render
    // you can practically copypaste for a simple program
    // prepare for render; set up tests
    glEnable(GL_STENCIL_TEST); // so clipping works
    glDisable(GL_DEPTH_TEST); // depth is not important to us

    // make sure we're in modelview matrix mode
    glMatrixMode(GL_MODELVIEW);
    // and reset the modelview matrix
    // that step is not important and in this example just more than unnecessary
    // so you can delete it if you want (just like the above glMatrixMode thing)
    glLoadIdentity();

    // render it up
    glEnable(GL_SCISSOR_TEST);
    desktop.Paint();
    glDisable(GL_SCISSOR_TEST);

    // note: glict pops every matrix it pushes
    // and undoes every transformation it does
    // so dont worry about that

    // now disable glstenciltest at the end
    // we dont put it up because we disabled it here
    // well, we disable it here because we might need it disabled... blah
    glDisable(GL_STENCIL_TEST);

    // make glut render it
    glutSwapBuffers();
}

// glut's way of telling program that window size changed
void reshape(int x, int y) {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glViewport(0,0,x,y);
    gluOrtho2D(0,x,0,y);

    // we rotate the entire coordinate sys because we use system opposite to GL's
    // (just flipped)
    // we do that for projection matrix so modelview can be reset whenever we want
    glRotatef(180., 1., 0., 0.);
    glTranslatef(0,-y,0.0);

    glMatrixMode(GL_MODELVIEW);


    // set desktop's width and height to what we got
    desktop.SetWidth(x);
    desktop.SetHeight(y);

	graphics.SetWidth(x);
	graphics.SetHeight(y - 122);
	graphics.SetOnPaint(onpaint);

	commander.SetWidth(x);
	commander.SetHeight(120);
	commander.SetPos(0,y-120);


	cmdconsole.SetWidth(x);
	cmdconsole.SetHeight(100);
	cmdconsole.SetPos(0,0);

	cmdconsoletext.SetWidth(x);
	cmdconsoletext.SetPos(0,0);

	command.SetWidth(x);
	command.SetHeight(20);
	command.SetPos(0,100);

	glictGlobals.w = x;
	glictGlobals.h = y;

	cmdconsole.SetVirtualSize(glictGlobals.w, 10*glictFontNumberOfLines(console.str().c_str()) + 10);

    glutPostRedisplay();
}

// glut's way of telling us a mouse-press event happened
void mouse(int button, int shift, int mousex, int mousey) {
    glictPos pos;
    pos.x = mousex; pos.y = mousey;

    if (shift==GLUT_DOWN) desktop.CastEvent(GLICT_MOUSEDOWN, &pos, 0);
    if (shift==GLUT_UP) desktop.CastEvent(GLICT_MOUSEUP, &pos, 0);
    glutPostRedisplay();
}

void MainWidgets() {
	glictGlobals.clippingMode = GLICT_SCISSORTEST;

	desktop.AddObject(&graphics);
	desktop.AddObject(&commander);
	commander.AddObject(&cmdconsole);
	commander.AddObject(&command);
	cmdconsole.AddObject(&cmdconsoletext);

	graphics.SetBGColor(.8,.8,1.,1.);
	commander.SetBGColor(.6,.6,.6,1.);
	cmdconsole.SetBGColor(.6,.6,.6,1.);
	command.SetBGColor(.5,.5,.5,1.);
	cmdconsoletext.SetBGColor(.6,.6,.6,1.);

	command.Focus(NULL);

}
bool collectionmode=false;
std::string collection;
void key(unsigned char key, int x, int y) {
	if (key != 13) {
		key = toupper(key);
		desktop.CastEvent(GLICT_KEYPRESS, &key, 0);
	} else {

		if (!collectionmode) {

			console << "? " << command.GetCaption() << std::endl;
			LogoInterpreter li(command.GetCaption());
			if (li.GetFirstToken().type != Token::TT_TO) {
				try {
					Token ret = li.Execute(globalcontext);
					if (ret.type != Token::TT_UNDEFINED) {
						LogoContext lc;
						throw std::string("You don't say what to do with ") + ret.Describe() + " (" + ret.Evaluate(globalcontext, lc).Describe() + ")";
					}
				} catch (std::string s) {
					console << "Error: " << s << std::endl;
				}
			} else {
				collection = command.GetCaption() + "\n";
				collectionmode = true;
			}

		} else {
			console << "> " << command.GetCaption() << std::endl;
			collection += command.GetCaption() + "\n";
			if (command.GetCaption() == "END") {
				try {
					LogoInterpreter li(collection);
					li.Execute(globalcontext);
				} catch (std::string s) {
					console << "Error: " << s << std::endl;
				}
				collectionmode = false;
			}
		}

		command.SetCaption("");
		cmdconsoletext.SetCaption(console.str());
		cmdconsoletext.SetHeight(10*glictFontNumberOfLines(console.str().c_str()));
		cmdconsole.SetVirtualSize(glictGlobals.w, 10*glictFontNumberOfLines(console.str().c_str()) + 10);
		cmdconsole.VirtualScrollBottom();

	}
    glutPostRedisplay();
}


int main(int argc, char** argv)
{

    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize (1200, 700);
    glutInitWindowPosition (0, 0);

    windowhandle = glutCreateWindow ("ONLogo");



	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
	//glLineWidth(1.2);
    MainWidgets();

	glictFont* sysfont = glictCreateFont("system");
	sysfont->SetFontParam(GLUT_STROKE_MONO_ROMAN);
	sysfont->SetRenderFunc(glutxStrokeString);
	sysfont->SetSizeFunc(glutxStrokeSize);

	console << "ONLogo v1.0" << std::endl << " (c) 2007 OBJECT Networks" << std::endl;
	cmdconsoletext.SetCaption(console.str());

	globalcontext.RegisterProcedure(std::string("PRINT"), prm_print , 1);
	globalcontext.RegisterProcedure(std::string("PR"), prm_print , 1);
	globalcontext.RegisterProcedure(std::string("POTS"), prm_pots , 0);
	globalcontext.RegisterProcedure(std::string("__VARIABLES"), prm___variables , 0);
	globalcontext.RegisterProcedure(std::string("MAKE"), prm_make, 2);
	globalcontext.RegisterProcedure(std::string("FORWARD"), prm_forward, 1);
	globalcontext.RegisterProcedure(std::string("FD"), prm_forward, 1);
	globalcontext.RegisterProcedure(std::string("RIGHT"), prm_right, 1);
	globalcontext.RegisterProcedure(std::string("RT"), prm_right, 1);
	globalcontext.RegisterProcedure(std::string("BACK"), prm_back, 1);
	globalcontext.RegisterProcedure(std::string("BK"), prm_back, 1);
	globalcontext.RegisterProcedure(std::string("LEFT"), prm_left, 1);
	globalcontext.RegisterProcedure(std::string("LT"), prm_left, 1);
	globalcontext.RegisterProcedure(std::string("SHOWTURTLE"), prm_showturtle, 0);
	globalcontext.RegisterProcedure(std::string("ST"), prm_showturtle, 0);
	globalcontext.RegisterProcedure(std::string("HIDETURTLE"), prm_hideturtle, 0);
	globalcontext.RegisterProcedure(std::string("HT"), prm_hideturtle, 0);
	globalcontext.RegisterProcedure(std::string("SETPC"), prm_setpc, 1);
	///////////////// procedures below need documentation ///////////////////
	globalcontext.RegisterProcedure(std::string("PENDOWN"), prm_pendown, 0);
	globalcontext.RegisterProcedure(std::string("PD"), prm_pendown, 0);
	globalcontext.RegisterProcedure(std::string("PENUP"), prm_penup, 0);
	globalcontext.RegisterProcedure(std::string("PU"), prm_penup, 0);
	globalcontext.RegisterProcedure(std::string("CLEARSCREEN"), prm_clearscreen, 0);
	globalcontext.RegisterProcedure(std::string("CS"), prm_clearscreen, 0);
	globalcontext.RegisterProcedure(std::string("REPEAT"), prm_repeat, 2);
	globalcontext.RegisterProcedure(std::string("FOR"), prm_for, 4);
	globalcontext.RegisterProcedure(std::string("IF"), prm_if, 2);
	globalcontext.RegisterProcedure(std::string("IFELSE"), prm_if, 3);
	globalcontext.RegisterProcedure(std::string("AND"), prm_and, 2);
	globalcontext.RegisterProcedure(std::string("BYE"), prm_bye, 0);

	{
		LogoInterpreter li("CLEARSCREEN");
		try {
			Token a = li.Execute(globalcontext);
		} catch (std::string s ) {
			std::cout << "Clearing screen failed: " << std::endl << " ==> " << s << std::endl;
		}

	}
	{
		std::fstream startupfile("startup.lgo");
		if (startupfile.is_open()) {
			std::string stext, startup;
			while (getline(startupfile, stext))
				startup = startup + stext + "\n";
			startupfile.close();
			std::cout << startup << std::endl;
			LogoInterpreter li(startup);
			try {
				Token a = li.Execute(globalcontext);
			} catch (std::string s ) {
				console << "Running startup logo file failed." << std::endl << " ==> " << s << std::endl;
			}

			cmdconsoletext.SetCaption(console.str());
			cmdconsoletext.SetHeight(10*glictFontNumberOfLines(console.str().c_str()));
			cmdconsole.SetVirtualSize(glictGlobals.w, 10*glictFontNumberOfLines(console.str().c_str()) + 10);
			cmdconsole.VirtualScrollBottom();

		}

	}

    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMouseFunc(mouse);

	glutKeyboardFunc(key);
    glutSetWindow(windowhandle);
    glutShowWindow();

    glutMainLoop();

	return 0;
}
